﻿/****************************************************************************
 * Description: 
 * 
 * Document: https://github.com/hiramtan/HiProtobuf
 * Author: hiramtan@live.com
 ****************************************************************************/

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace CSharpGeneratorForProton
{
    internal class ProtoGenerater
    {
        private const string kStrProtoHeader = @"
// This is auto generated by Protobuf
// 此文件由CSharpGeneratorForProton工具自动生成
// Support: hiramtan@live.com

// [START declaration]
syntax = ""proto3"";
package Protobuf;
// [END declaration]

// [START java_declaration]
option java_package = ""com.proton.protobuf"";
option java_outer_classname = ""{0}"";
// [END java_declaration]

// [START csharp_declaration]
option csharp_namespace = ""CSharpGeneratorForProton.Protobuf""; 
// [END csharp_declaration]
";
        private const string kStrClassFormat = "message {0}\n";
        private const string kStrClassStart = "{\n";
        private const string kStrClassEnd = "}\n\n";
        private const string kStrTable = "    ";
        private const string kStrLineEnd = ";\n";
        private const string kStrDescription = "// {0}\n";

        private const string kRoot = "root";
        private const string kItem = "item";
        public const string kExportFile = "exportfile";
        private const string kSchema = "schema";
        private const string kSuffix = ".proto";

        private static readonly Dictionary<string, string> baseTypes = new Dictionary<string, string>()
        {
            ["int"] = "int32",
            ["double"] = "double",
            ["string"] = "string",
            ["bool"] = "bool",
            ["long"] = "int64",
            ["float"] = "float",
        };

        private Worker.Args args_;
        private string root_;
        private string item_;
        private string exportFile_;
        private JObject schemaObj_;
        private string className_;
        private bool isItemTable;
        private string outPath_;

        public ProtoGenerater(Worker.Args arg, JObject obj)
        {
            args_ = arg;
            root_ = obj[kRoot].ToString();
            item_ = obj[kItem].ToString();
            exportFile_ = obj[kExportFile].ToString();
            schemaObj_ = (JObject)obj[kSchema];
            className_ = item_.ToFirstCharUpper();
            if (!string.IsNullOrEmpty(args_.Suffix))
            {
                className_ += args_.Suffix.ToFirstCharUpper();
            }
            isItemTable = root_.Contains(item_ + 's');          //判断是格式1,还是格式二
            string protoFolder = Path.Combine(Settings.Export_Folder, Settings.proto_folder);
            if (!Directory.Exists(protoFolder))
                Directory.CreateDirectory(protoFolder);
            outPath_ = Path.Combine(protoFolder, root_ + kSuffix);
        }

        public void Process()
        {
            TryClear();
            ProcessHeader();
            ProcessVariables();
        }

        void TryClear()
        {
            try
            {
                if (!File.Exists(outPath_))
                    return;

                File.Delete(outPath_);
            }
            catch (Exception)
            {
                File.WriteAllText(outPath_, "");
                //throw;
            }
        }

        void ProcessHeader()
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendFormat(kStrProtoHeader, className_);
            var sw = File.AppendText(outPath_);
            sw.WriteLine(builder.ToString());
            sw.Close();
        }

        void ProcessVariables()
        {
            StringBuilder builder = Execute(className_, schemaObj_);
            var sw = File.AppendText(outPath_);
            sw.WriteLine(builder.ToString());
            sw.Close();
        }

        private StringBuilder Execute(string className, JObject schema, string table = "")
        {
            int index = 0;
            // 二级嵌套需要加_ 如 message Class_
            className = string.IsNullOrEmpty(table) ? className.ToFirstCharUpper() : (className.ToFirstCharUpper() + "_");
            Dictionary<string, JObject> childs = new Dictionary<string, JObject>();
            List<StringBuilder> builders = new List<StringBuilder>();
            StringBuilder builder = new StringBuilder();
            // 添加message头
            builder.Append(table);
            builder.AppendFormat(kStrClassFormat, className);
            builder.Append(table);
            builder.Append(kStrClassStart);
            foreach (var obj in schema)
            {
                string name = obj.Key;
                JArray value = (JArray)obj.Value;
                GetTypeInfo(value, out JToken itemType, out string itemDescription);
                string typeName = Build(name, itemType, childs);
                StringBuilder property = AddProperty(name, typeName, itemDescription, table, ref index);
                // 这里不马上添加进行保存
                builders.Add(property);
            }

            // 先添加子类型
            foreach (var kv in childs)
            {
                builder.Append(Execute(kv.Key, kv.Value, table + kStrTable));
            }

            // 在添加属性
            foreach (var property in builders)
            {
                builder.Append(table);
                builder.Append(property);
            }

            // 添加message尾
            builder.Append(table);
            builder.Append(kStrClassEnd);

            return builder;
        }

        private void GetTypeInfo(JArray a, out JToken type, out string description)
        {
            type = a[0];
            description = a.Count > 1 ? a[1].ToString() : null;
        }

        private void BuildObject(string name)
        {
            //return typeDeclaration;
        }

        private string BuildArray(string name, JArray array, Dictionary<string, JObject> childs)
        {
            GetTypeInfo(array, out JToken baseType, out string _);
            string baseName = name.Remove(name.Length - 1);
            return Build(baseName, baseType, childs) + "[]";
        }

        private string Build(string name, JToken type, Dictionary<string, JObject> childs = null)
        {
            switch (type.Type)
            {
                case JTokenType.String:
                    {
                        string t = type.ToString();
                        if (!baseTypes.ContainsKey(t))
                        {
                            throw new NotSupportedException(string.Format("proto不支持类型class {0} => {1} = {2}", className_, t, name));
                        }
                        return baseTypes[t];
                    }
                case JTokenType.Array:
                    {
                        return BuildArray(name, (JArray)type, childs);
                    }
                case JTokenType.Object:
                    {
                        if (childs != null)
                            childs.Add(name, (JObject)type);
                        return name.ToFirstCharUpper() + "_";
                    }
                default:
                    {
                        throw new NotSupportedException();
                    }
            }
        }

        private StringBuilder AddProperty(string name, string typeName, string description, string table, ref int index)
        {
            index++;//从1开始定义
            table += kStrTable;
            StringBuilder builder = new StringBuilder();
            builder.Append(kStrTable);
            builder.AppendFormat(kStrDescription, description);
            if (typeName.Contains("[]"))//如果是数组进行转换
            {
                typeName = "repeated " + typeName.Split('[')[0];
            }
            builder.AppendFormat("{0}{1} {2} = {3}{4}", table, typeName, name, index, kStrLineEnd);
            return builder;
        }
    }
}
